package net.kesumu.yushabu.utils;

import static android.opengl.GLES20.GL_COLOR_BUFFER_BIT;
import static android.opengl.GLES20.glClear;
import static android.opengl.GLES20.glClearColor;
import static android.opengl.GLES20.glViewport;
import static android.opengl.Matrix.invertM;
import static android.opengl.Matrix.multiplyMM;
import static android.opengl.Matrix.multiplyMV;
import static android.opengl.Matrix.rotateM;
import static android.opengl.Matrix.scaleM;
import static android.opengl.Matrix.setIdentityM;
import static android.opengl.Matrix.setLookAtM;
import static android.opengl.Matrix.translateM;
import static net.kesumu.general.android.opengl.object.YushabuMessageConstants.*;

import java.util.ArrayList;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;







import net.kesumu.general.android.EasyStorage.EasyStorage;
import net.kesumu.general.android.opengl.object.Geometry;
import net.kesumu.general.android.opengl.object.Geometry.Point;
import net.kesumu.general.android.opengl.object.Geometry.Ray;
import net.kesumu.general.android.opengl.object.Geometry.Sphere;
import net.kesumu.general.android.opengl.object.TextureCircle;
import net.kesumu.general.android.opengl.object.TextureRingSector;
import net.kesumu.general.android.opengl.object.YushabuBackground;
import net.kesumu.general.android.opengl.object.YushabuHistoryBackground;
import net.kesumu.general.android.opengl.object.YushabuHistoryBar;
import net.kesumu.general.android.opengl.object.YushabuHistoryLine;
import net.kesumu.general.android.opengl.object.YushabuMessageContent;
import net.kesumu.general.android.opengl.object.YushabuMessageItem;
import net.kesumu.general.android.opengl.object.YushabuMessageLine;
import net.kesumu.general.android.opengl.object.YushabuPhoto;
import net.kesumu.general.android.opengl.object.YushabuPhotoBackgound;
import net.kesumu.general.android.opengl.program.ColorShaderProgram;
import net.kesumu.general.android.opengl.program.TextBitmap;
import net.kesumu.general.android.opengl.program.TextureHelper;
import net.kesumu.general.android.opengl.program.TextureShaderProgram;
import net.kesumu.general.android.opengl.util.MatrixHelper;
import net.kesumu.general.android.view.BackgroundUtil;
import net.kesumu.yushabuserver.entity.User;
import net.kesumu.yushabuserver.entity.YushabuMessage;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.os.SystemClock;
import android.widget.ImageView;
import net.kesumu.yushabu.R;
import net.kesumu.yushabu.YushabuMessagingActivity;

public class YushabuMessagingGLRenderer implements GLSurfaceView.Renderer{
    
    public static final String TAG = "YushabuMessagingGLRenderer";

    private final YushabuMessagingActivity context;
    private EasyStorage mEasyStorage;
    
    private final float[] projectionMatrix = new float[16];
    private final float[] modelMatrix = new float[16];
    private final float[] viewMatrix = new float[16];
    private final float[] viewProjectionMatrix = new float[16];
    private final float[] invertedViewProjectionMatrix = new float[16];
    private final float[] modelViewProjectionMatrix = new float[16];
    
    /*private Table table;
    private Mallet mallet;
    private Puck puck;*/
    
//    private Table table;
    
    private final int MESSAGE_MAX_MUN = 5;
    Point messageLineEndPoint;
    
    private final TextBitmap textBitmap;
    
    private Point mostLeftUpPoint;
    
    private Point historyIconPoint;
    
    YushabuBackground yushabuBackground;
    TextureCircle yushabuHistoryIcon;
    TextureRingSector yushabuHistoryIconText;
    YushabuHistoryLine yushabuHistoryLine;
    YushabuHistoryBar yushabuHistoryBar;
    YushabuHistoryBackground yushabuHistoryBackground;
    ArrayList<YushabuMessageItem> yushabuMessageItems = new ArrayList<YushabuMessageItem>();
    ArrayList<YushabuMessageContent> yushabuMessageContents = new ArrayList<YushabuMessageContent>();
    ArrayList<Point> photoCenterPoints = new ArrayList<Point>();
    ArrayList<YushabuPhotoBackgound> yushabuPhotoBackgounds = new ArrayList<YushabuPhotoBackgound>();
    ArrayList<YushabuPhoto> yushabuPhotos = new ArrayList<YushabuPhoto>();
    YushabuMessageLine yushabuMessageLine;
    
    private TextureShaderProgram textureProgram;
    private ColorShaderProgram colorProgram;    
    
    private int texture;
    private int leftTexture;
    
    // Create a rotation transformation for the triangle
    private long previousTime = 0;
    private long currentTime = 0;
    
    public YushabuMessagingGLRenderer(YushabuMessagingActivity context) {
        this.context = context;
        this.textBitmap = new TextBitmap(this.context);
    }

    @Override
    public void onSurfaceCreated(GL10 unused, EGLConfig config) {
        // Set the background frame color
        glClearColor(1f, 1f, 1f, 1f);
        GLES20.glEnable(GLES20.GL_BLEND);
        GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA);

        /*table = new Table();
        mallet = new Mallet(0.08f, 0.15f, 32);
        puck = new Puck(0.06f, 0.02f, 32);*/
//        table = new Table();
        mEasyStorage = new EasyStorage(context, context.getString(R.string.preference_file_key));
        
        photoCenterPoints.add(PHOTO_BASE_POINT_1);
        photoCenterPoints.add(PHOTO_BASE_POINT_2);
        photoCenterPoints.add(PHOTO_BASE_POINT_3);
        photoCenterPoints.add(PHOTO_BASE_POINT_4);
        photoCenterPoints.add(PHOTO_BASE_POINT_5);
//        texture = TextureHelper.loadTexture(context, textBitmap.buildTextBitmap("~~~", 255, 1));
        texture = TextureHelper.loadTexture(context, textBitmap.buildIconTextBitmap(context.getString(R.string.historyMessage)));
        yushabuHistoryIconText = new TextureRingSector(texture, 32);
        float angleInRadians = 
                ((float) 22f / (float) 360f)
              * ((float) Math.PI * 2f);
        historyIconPoint = new Point(
                (float)((BASE_RADIUS - HISTORY_BAR_BG_WIDTH/2f) * Math.sin(angleInRadians))
                ,-(float)((BASE_RADIUS - HISTORY_BAR_BG_WIDTH/2f) * Math.cos(angleInRadians))
                ,0f);
        texture = TextureHelper.loadTexture(context, R.drawable.history_icon);
        yushabuHistoryIcon = new TextureCircle(new Point(0,0,0), texture, 32);
        yushabuHistoryLine = new YushabuHistoryLine(context.getResources().getColor(R.color.purple), 64);
        yushabuHistoryBar = new YushabuHistoryBar(context.getResources().getColor(R.color.blue), 64);
        yushabuHistoryBackground = new YushabuHistoryBackground(context.getResources().getColor(R.color.green), 64);
        
        textureProgram = new TextureShaderProgram(context);
        colorProgram = new ColorShaderProgram(context,"varying");        
        
//        texture = TextureHelper.loadTexture(context, R.drawable.air_hockey_surface);
    }

    @Override
    public void onSurfaceChanged(GL10 unused, int width, int height) {
        /*glViewport(0, 0, width, height);
        
        MatrixHelper.perspectiveM(projectionMatrix, 45, (float) width
                / (float) height, 1f, 10f);
        
        setIdentityM(modelMatrix, 0);
        translateM(modelMatrix, 0, 0f, 0f, -2.5f);        
        rotateM(modelMatrix, 0, -60f, 1f, 0f, 0f);
        
        final float[] temp = new float[16];
        multiplyMM(temp, 0, projectionMatrix, 0, modelMatrix, 0);        
        System.arraycopy(temp, 0, projectionMatrix, 0, temp.length);*/
        
        mostLeftUpPoint = new Point(-1.07f, ((float) height / (float) width) + 0.1f, 0f);
        texture = TextureHelper.loadTexture(context, R.drawable.bg_messages);
        yushabuBackground = new YushabuBackground(texture, new Point(0f, 0f, 0f), -2f*mostLeftUpPoint.x, 2*mostLeftUpPoint.y);
        
        // Set the OpenGL viewport to fill the entire surface.
        glViewport(0, 0, width, height);
        MatrixHelper.perspectiveM(projectionMatrix, 43, (float) width
            / (float) height, 1f, 10f);
        setLookAtM(viewMatrix, 0, 0f, 0f, 4f, 0f, 0f, 0f, 0f, 1f, 0f);
    }
    
    @Override
    public void onDrawFrame(GL10 unused) {
        // Clear the rendering surface.
        glClear(GL_COLOR_BUFFER_BIT);
        
        // Multiply the view and projection matrices together.
        multiplyMM(viewProjectionMatrix, 0, projectionMatrix, 0, viewMatrix, 0);
        invertM(invertedViewProjectionMatrix, 0, viewProjectionMatrix, 0);

        /*// Draw the table.
        rotateObjectInScene(-90f, 1f, 0f, 0f);
        textureProgram.useProgram();
        textureProgram.setUniforms(modelViewProjectionMatrix, texture);
        table.bindData(textureProgram);
        table.draw();
        
        // Draw the mallets.
        positionObjectInScene(0f, mallet.height / 2f, -0.4f);
        colorProgram.useProgram();
        colorProgram.setUniforms(modelViewProjectionMatrix, 1f, 0f, 0f);
        mallet.bindData(colorProgram);
        mallet.draw();

        positionObjectInScene(0f, mallet.height / 2f, 0.4f);
        colorProgram.setUniforms(modelViewProjectionMatrix, 0f, 0f, 1f);
        // Note that we don't have to define the object data twice -- we just
        // draw the same mallet again but in a different position and with a
        // different color.
        mallet.draw();

        // Draw the puck.
        positionObjectInScene(0f, puck.height / 2f, 0f);
        colorProgram.setUniforms(modelViewProjectionMatrix, 0.8f, 0.8f, 1f);
        puck.bindData(colorProgram);
        puck.draw();*/
        
        textureProgram.useProgram();
        // Draw the background.
        resetModelViewProjectionMatrix();
//        positionObjectInScene(0.5f, 0.5f, 0.5f);
        positionObjectInScene(mostLeftUpPoint.x, -mostLeftUpPoint.y, mostLeftUpPoint.z);
        textureProgram.useProgram();
        textureProgram.setUniforms(modelViewProjectionMatrix, yushabuBackground.texture);
        yushabuBackground.bindData(textureProgram);
        yushabuBackground.draw();
        
        colorProgram.useProgram();
        for(int i=yushabuMessageItems.size()-1;i>=0;i--) {
            switch(yushabuMessageItems.get(i).state) {
                case (YushabuMessageItem.LEFT):
                    resetModelViewProjectionMatrix();
                    positionObjectInScene(mostLeftUpPoint.x, mostLeftUpPoint.y, mostLeftUpPoint.z);
                    colorProgram.setUniforms(modelViewProjectionMatrix);
                    yushabuMessageItems.get(i).leftBindData(colorProgram);
                    yushabuMessageItems.get(i).leftDraw();
                    break;
                
            }
        }
        
        textureProgram.useProgram();
        for(int i=yushabuMessageContents.size()-1;i>=0;i--) {
            switch(yushabuMessageContents.get(i).state) {
            case (YushabuMessageContent.LEFT):
                resetModelViewProjectionMatrix();
                positionObjectInScene(mostLeftUpPoint.x, mostLeftUpPoint.y, mostLeftUpPoint.z);
                textureProgram.setUniforms(modelViewProjectionMatrix, yushabuMessageContents.get(i).leftTexture);
                yushabuMessageContents.get(i).bindData(textureProgram);
                yushabuMessageContents.get(i).draw();
                break;
            }
        }
        
        if(yushabuMessageLine != null) {
            colorProgram.useProgram();
            //Draw the message line
            resetModelViewProjectionMatrix();
            positionObjectInScene(mostLeftUpPoint.x, mostLeftUpPoint.y, mostLeftUpPoint.z);
            colorProgram.setUniforms(modelViewProjectionMatrix);
            yushabuMessageLine.bindData(colorProgram);
            yushabuMessageLine.draw();
        }
        
        colorProgram.useProgram();
        for(int i=yushabuMessageItems.size()-1;i>=0;i--) {
            switch(yushabuMessageItems.get(i).state) {
                case (YushabuMessageItem.APPEARING):
                    resetModelViewProjectionMatrix();
                    positionObjectInScene(mostLeftUpPoint.x, mostLeftUpPoint.y, mostLeftUpPoint.z);
                    rotateObjectInScene(yushabuMessageItems.get(i).appearingRotateAngle,0f,0f,1f);
                    colorProgram.setUniforms(modelViewProjectionMatrix);
                    yushabuMessageItems.get(i).bindData(colorProgram);
                    yushabuMessageItems.get(i).draw();
                    if(yushabuMessageItems.get(i).appearingRotateAngle > 1f) {
                        float delt = deltTimedChange(5f,2);
                        if( delt != 0f) {
                            yushabuMessageItems.get(i).appearingRotateAngle -= delt;
                            updateAppearingMessageLine();
                        }
                    }else {
                        yushabuMessageItems.get(i).state = YushabuMessageItem.APPEARED;
                    }
                    break;
                case (YushabuMessageItem.LEFT):
                    break;
                default:
                    resetModelViewProjectionMatrix();
                    positionObjectInScene(mostLeftUpPoint.x, mostLeftUpPoint.y, mostLeftUpPoint.z);
                    colorProgram.setUniforms(modelViewProjectionMatrix);
                    yushabuMessageItems.get(i).bindData(colorProgram);
                    yushabuMessageItems.get(i).draw();
            }
        }
        
        textureProgram.useProgram();
        for(int i=yushabuMessageContents.size()-1;i>=0;i--) {
            switch(yushabuMessageContents.get(i).state) {
            case (YushabuMessageContent.APPEARING):
                resetModelViewProjectionMatrix();
                positionObjectInScene(mostLeftUpPoint.x, mostLeftUpPoint.y, mostLeftUpPoint.z);
                rotateObjectInScene(yushabuMessageItems.get(i).appearingRotateAngle,0f,0f,1f);
                textureProgram.setUniforms(modelViewProjectionMatrix, yushabuMessageContents.get(i).texture);
                yushabuMessageContents.get(i).bindData(textureProgram);
                yushabuMessageContents.get(i).draw();
                if(yushabuMessageItems.get(i).appearingRotateAngle < 1f) {
                    yushabuMessageContents.get(i).state = YushabuMessageContent.APPEARED;
                }
                break;
            case (YushabuMessageContent.LEFT):
                break;
            default:
                resetModelViewProjectionMatrix();
                positionObjectInScene(mostLeftUpPoint.x, mostLeftUpPoint.y, mostLeftUpPoint.z);
                textureProgram.setUniforms(modelViewProjectionMatrix, yushabuMessageContents.get(i).texture);
                yushabuMessageContents.get(i).bindData(textureProgram);
                yushabuMessageContents.get(i).draw();
            }
        }
        
        
        colorProgram.useProgram();
        for(int i=0;i<yushabuPhotoBackgounds.size();i++) {
            switch(yushabuPhotoBackgounds.get(i).state) {
                case (YushabuPhotoBackgound.UNFOCUSING):
                    resetModelViewProjectionMatrix();
                    positionObjectInScene(mostLeftUpPoint.x + photoCenterPoints.get(i).x
                                        ,mostLeftUpPoint.y + photoCenterPoints.get(i).y
                                        ,mostLeftUpPoint.z + photoCenterPoints.get(i).z);
                    scaleObjectInScene(yushabuPhotoBackgounds.get(i).focusingScaleFactor
                                        , yushabuPhotoBackgounds.get(i).focusingScaleFactor
                                        , 1f);
                    colorProgram.setUniforms(modelViewProjectionMatrix);
                    yushabuPhotoBackgounds.get(i).bindData(colorProgram);
                    yushabuPhotoBackgounds.get(i).draw();
                    if(yushabuPhotoBackgounds.get(i).focusingScaleFactor > 1f) {
                        float delt = deltTimedChange(yushabuPhotoBackgounds.get(i).focusedScaleFactorDelt,1);
                        if( delt != 0f) {
                            yushabuPhotoBackgounds.get(i).focusingScaleFactor -= delt;
                        }
                    }else {
                        yushabuPhotoBackgounds.get(i).state = YushabuPhotoBackgound.UNFOCUSED;
                    }
                    break;
                case (YushabuPhotoBackgound.FOCUSING):
                    resetModelViewProjectionMatrix();
                    positionObjectInScene(mostLeftUpPoint.x + photoCenterPoints.get(i).x
                                         ,mostLeftUpPoint.y + photoCenterPoints.get(i).y
                                         ,mostLeftUpPoint.z + photoCenterPoints.get(i).z);
                    scaleObjectInScene(yushabuPhotoBackgounds.get(i).focusingScaleFactor
                                        , yushabuPhotoBackgounds.get(i).focusingScaleFactor
                                        , 1f);
                    colorProgram.setUniforms(modelViewProjectionMatrix);
                    yushabuPhotoBackgounds.get(i).bindData(colorProgram);
                    yushabuPhotoBackgounds.get(i).draw();  
                    if(yushabuPhotoBackgounds.get(i).focusingScaleFactor < yushabuPhotoBackgounds.get(i).focusedScaleFactor) {
                        float delt = deltTimedChange(yushabuPhotoBackgounds.get(i).focusedScaleFactorDelt,1);
                        if( delt != 0f) {
                            yushabuPhotoBackgounds.get(i).focusingScaleFactor += delt;
                        }
                    }else {
                        yushabuPhotoBackgounds.get(i).state = YushabuPhotoBackgound.FOCUSED;
                    }
                    break;
                case (YushabuPhotoBackgound.FOCUSED):
                    resetModelViewProjectionMatrix();
                    positionObjectInScene(mostLeftUpPoint.x + photoCenterPoints.get(i).x
                                        ,mostLeftUpPoint.y + photoCenterPoints.get(i).y
                                        ,mostLeftUpPoint.z + photoCenterPoints.get(i).z);
                    colorProgram.setUniforms(modelViewProjectionMatrix);
                    yushabuPhotoBackgounds.get(i).focusedBindData(colorProgram);
                    yushabuPhotoBackgounds.get(i).focusedDraw();
                    break;
                default:
                    resetModelViewProjectionMatrix();
                    positionObjectInScene(mostLeftUpPoint.x + photoCenterPoints.get(i).x
                                        ,mostLeftUpPoint.y + photoCenterPoints.get(i).y
                                        ,mostLeftUpPoint.z + photoCenterPoints.get(i).z);
                    colorProgram.setUniforms(modelViewProjectionMatrix);
                    yushabuPhotoBackgounds.get(i).bindData(colorProgram);
                    yushabuPhotoBackgounds.get(i).draw();
            }
        }
        
        textureProgram.useProgram();
        for(int i=0;i<yushabuPhotos.size();i++) {
            switch(yushabuPhotos.get(i).state) {
                case (YushabuPhoto.UNFOCUSING):
                    resetModelViewProjectionMatrix();
                    positionObjectInScene(mostLeftUpPoint.x + photoCenterPoints.get(i).x
                                        ,mostLeftUpPoint.y + photoCenterPoints.get(i).y
                                        ,mostLeftUpPoint.z + photoCenterPoints.get(i).z);
                    scaleObjectInScene(yushabuPhotoBackgounds.get(i).focusingScaleFactor
                                       , yushabuPhotoBackgounds.get(i).focusingScaleFactor
                                       , 1f);
                    textureProgram.setUniforms(modelViewProjectionMatrix, yushabuPhotos.get(i).texture);
                    yushabuPhotos.get(i).bindData(textureProgram);
                    yushabuPhotos.get(i).draw();  
                    if(yushabuPhotoBackgounds.get(i).focusingScaleFactor <= 1f) {
                        yushabuPhotos.get(i).state = YushabuPhoto.UNFOCUSED;
                    }
                break;
                case (YushabuPhoto.FOCUSING):
                    resetModelViewProjectionMatrix();
                    positionObjectInScene(mostLeftUpPoint.x + photoCenterPoints.get(i).x
                                        ,mostLeftUpPoint.y + photoCenterPoints.get(i).y
                                        ,mostLeftUpPoint.z + photoCenterPoints.get(i).z);
                    scaleObjectInScene(yushabuPhotoBackgounds.get(i).focusingScaleFactor
                                        , yushabuPhotoBackgounds.get(i).focusingScaleFactor
                                        , 1f);
                    textureProgram.setUniforms(modelViewProjectionMatrix, yushabuPhotos.get(i).texture);
                    yushabuPhotos.get(i).bindData(textureProgram);
                    yushabuPhotos.get(i).draw();  
                    if(yushabuPhotoBackgounds.get(i).focusingScaleFactor >= yushabuPhotoBackgounds.get(i).focusedScaleFactor) {
                        yushabuPhotos.get(i).state = YushabuPhoto.FOCUSED;
                    }
                    break;
                case (YushabuPhoto.FOCUSED):
                    resetModelViewProjectionMatrix();
                    positionObjectInScene(mostLeftUpPoint.x + photoCenterPoints.get(i).x
                                        ,mostLeftUpPoint.y + photoCenterPoints.get(i).y
                                        ,mostLeftUpPoint.z + photoCenterPoints.get(i).z);
                    textureProgram.setUniforms(modelViewProjectionMatrix, yushabuPhotos.get(i).texture);
                    yushabuPhotos.get(i).focusedBindData(textureProgram);
                    yushabuPhotos.get(i).focusedDraw();
                    break;
                default:
                    resetModelViewProjectionMatrix();
                    positionObjectInScene(mostLeftUpPoint.x + photoCenterPoints.get(i).x
                                        ,mostLeftUpPoint.y + photoCenterPoints.get(i).y
                                        ,mostLeftUpPoint.z + photoCenterPoints.get(i).z);
                    textureProgram.setUniforms(modelViewProjectionMatrix, yushabuPhotos.get(i).texture);
                    yushabuPhotos.get(i).bindData(textureProgram);
                    yushabuPhotos.get(i).draw();  
            }
            
        }
        

        colorProgram.useProgram();
        //Draw the history background
        resetModelViewProjectionMatrix();
        positionObjectInScene(mostLeftUpPoint.x, mostLeftUpPoint.y, mostLeftUpPoint.z);
        colorProgram.setUniforms(modelViewProjectionMatrix);
        yushabuHistoryBackground.bindData(colorProgram);
        yushabuHistoryBackground.draw();
        
        //Draw the history line
        resetModelViewProjectionMatrix();
        positionObjectInScene(mostLeftUpPoint.x, mostLeftUpPoint.y, mostLeftUpPoint.z);
        colorProgram.setUniforms(modelViewProjectionMatrix);
        yushabuHistoryLine.bindData(colorProgram);
        yushabuHistoryLine.draw();
        
        //Draw the history bar
        resetModelViewProjectionMatrix();
        positionObjectInScene(mostLeftUpPoint.x, mostLeftUpPoint.y, mostLeftUpPoint.z);
        colorProgram.setUniforms(modelViewProjectionMatrix);
        yushabuHistoryBar.bindData(colorProgram);
        yushabuHistoryBar.draw();
        
        //Draw the history icon
        textureProgram.useProgram();
        resetModelViewProjectionMatrix();
        positionObjectInScene(mostLeftUpPoint.x + historyIconPoint.x
                ,mostLeftUpPoint.y + historyIconPoint.y
                ,mostLeftUpPoint.z + historyIconPoint.z);
        textureProgram.setUniforms(modelViewProjectionMatrix, yushabuHistoryIcon.texture);
        yushabuHistoryIcon.bindData(textureProgram);
        yushabuHistoryIcon.draw();
        
        //Draw the history icon text
        textureProgram.useProgram();
        resetModelViewProjectionMatrix();
        positionObjectInScene(mostLeftUpPoint.x, mostLeftUpPoint.y, mostLeftUpPoint.z);
        textureProgram.setUniforms(modelViewProjectionMatrix, yushabuHistoryIconText.texture);
        yushabuHistoryIconText.bindData(textureProgram);
        yushabuHistoryIconText.draw();
    }
    
    private void positionTableInScene() {
        // The table is defined in terms of X & Y coordinates, so we rotate it
        // 90 degrees to lie flat on the XZ plane.
        setIdentityM(modelMatrix, 0);
        rotateM(modelMatrix, 0, -90f, 1f, 0f, 0f);
        multiplyMM(modelViewProjectionMatrix, 0, viewProjectionMatrix,
            0, modelMatrix, 0);
    }
    
    private void rotateObjectInScene(float angle, float x, float y, float z) {
        // The table is defined in terms of X & Y coordinates, so we rotate it
        // 90 degrees to lie flat on the XZ plane.
        float[] modelViewProjectionMatrixCopy = new float[16];
        float[] temp = new float[16];
        setIdentityM(temp, 0);
        multiplyMM(modelViewProjectionMatrixCopy, 0, temp,
            0, modelViewProjectionMatrix, 0);
        
        setIdentityM(modelMatrix, 0);
        rotateM(modelMatrix, 0, angle, x, y, z);
        multiplyMM(modelViewProjectionMatrix, 0, modelViewProjectionMatrixCopy,
            0, modelMatrix, 0);
    }

    // The mallets and the puck are positioned on the same plane as the table.
    private void positionObjectInScene(float x, float y, float z) {
        float[] modelViewProjectionMatrixCopy = new float[16];
        float[] temp = new float[16];
        setIdentityM(temp, 0);
        multiplyMM(modelViewProjectionMatrixCopy, 0, temp,
            0, modelViewProjectionMatrix, 0);
        
        setIdentityM(modelMatrix, 0);
        translateM(modelMatrix, 0, x, y, z);
        multiplyMM(modelViewProjectionMatrix, 0, modelViewProjectionMatrixCopy,
            0, modelMatrix, 0);
    }
    
    private void scaleObjectInScene(float x, float y, float z) {
        float[] modelViewProjectionMatrixCopy = new float[16];
        float[] temp = new float[16];
        setIdentityM(temp, 0);
        multiplyMM(modelViewProjectionMatrixCopy, 0, temp,
            0, modelViewProjectionMatrix, 0);
        
        setIdentityM(modelMatrix, 0);
        scaleM(modelMatrix, 0, x, y, z);
        multiplyMM(modelViewProjectionMatrix, 0, modelViewProjectionMatrixCopy,
            0, modelMatrix, 0);
    }
    
    private void resetModelViewProjectionMatrix() {
        float[] temp = new float[16];
        setIdentityM(temp, 0);
        multiplyMM(modelViewProjectionMatrix, 0, viewProjectionMatrix,
            0, temp, 0);
    }
    
    /*float animatedAngle = 0;
    boolean animating = false;
    private void animatedAppearing(int i) {
        if(animatedAngle < 90f) {
            animatedAngle += 1f;
            rotateObjectInScene(animatedAngle,0f,0f,1f);
            positionObjectInScene(mostLeftUpPoint.x, mostLeftUpPoint.y, mostLeftUpPoint.z);
            colorProgram.setUniforms(modelViewProjectionMatrix);
            yushabuMessageItems.get(i).bindData(colorProgram);
            yushabuMessageItems.get(i).draw();
        }else{
            yushabuMessageItems.get(i).state = YushabuMessageItem.APPEARED;
            animating = true;
        }
    }*/
    
    static int focusingUserId = 1;
    public void appendMessage(YushabuMessage yushabuMessage) {
//        int currentUserId = (focusingUserId++)%yushabuPhotos.size() + 1;
        int currentUserId = yushabuMessage.getUserId();
        int userColor = 0;
        for(int i=0;i<yushabuPhotos.size();i++) {
            if(currentUserId == yushabuPhotos.get(i).userId) {
                userColor = yushabuPhotoBackgounds.get(i).color;
                break;
            }
        }
        YushabuMessageItem newYMI = null;
        YushabuMessageContent newYMC = null;
        
        YushabuMessageItem oldYMI = null;
        YushabuMessageContent oldYMC = null;
        
        /*texture = TextureHelper.loadTexture(context, textBitmap.buildTextBitmap(yushabuMessage.getContent(), 255,1));
        leftTexture = TextureHelper.loadTexture(context, textBitmap.buildTextBitmap(yushabuMessage.getContent(), 64,1));
        if(texture == 0 || leftTexture == 0) {
            return;
        }*/
        
        if(yushabuMessageItems.size() == MESSAGE_MAX_MUN) {
            for(int i=0;i<yushabuMessageItems.size();i++) {
                yushabuMessageItems.get(i).state = YushabuMessageItem.LEFT;
            }
            for(int i=0;i<yushabuMessageContents.size();i++) {
                yushabuMessageContents.get(i).state = YushabuMessageContent.LEFT;
            }
            
            newYMI = new YushabuMessageItem(0
                    , userColor, 32);
            texture = TextureHelper.loadTexture(context, textBitmap.buildTextBitmap(yushabuMessage.getContent(), 255, 1));
            leftTexture = TextureHelper.loadTexture(context, textBitmap.buildTextBitmap(yushabuMessage.getContent(), 64, 1));
            newYMC = new YushabuMessageContent(yushabuMessage.getContent(), 0, texture, leftTexture, 32);
            
            newYMI.state = YushabuMessageItem.APPEARING;
            newYMC.state = YushabuMessageContent.APPEARING;
            
            oldYMI = yushabuMessageItems.get(0);
            oldYMC = yushabuMessageContents.get(0);
            oldYMI.state = YushabuMessageItem.LEFT;
            oldYMC.state = YushabuMessageContent.LEFT;
            yushabuMessageItems.add(oldYMI);
            yushabuMessageContents.add(oldYMC);
            
            yushabuMessageItems.set(0, newYMI);
            yushabuMessageContents.set(0, newYMC);
            
            
        }else if(yushabuMessageItems.size() == MESSAGE_MAX_MUN+1){
            for(int i=0;i<MESSAGE_MAX_MUN;i++) {
                if(yushabuMessageItems.get(i).state == YushabuMessageItem.APPEARING) {
                    yushabuMessageItems.get(i).state = YushabuMessageItem.APPEARED;
                }
                if(yushabuMessageContents.get(i).state == YushabuMessageContent.APPEARING) {
                    yushabuMessageContents.get(i).state = YushabuMessageContent.APPEARED;
                }
            }
            for(int i=0;i<MESSAGE_MAX_MUN+1;i++) {
                if(yushabuMessageItems.get(i).state == YushabuMessageItem.LEFT) {
                    if(i<MESSAGE_MAX_MUN) {
                        newYMI = new YushabuMessageItem(i
                                , userColor, 32);
                        texture = TextureHelper.loadTexture(context, textBitmap.buildTextBitmap(yushabuMessage.getContent(), 255, i+1));
                        leftTexture = TextureHelper.loadTexture(context, textBitmap.buildTextBitmap(yushabuMessage.getContent(), 64, i+1));
                        newYMC = new YushabuMessageContent(yushabuMessage.getContent(), i, texture, leftTexture, 32);
                        
                        newYMI.state = YushabuMessageItem.APPEARING;
                        newYMC.state = YushabuMessageContent.APPEARING;
                        
                        
                        oldYMI = yushabuMessageItems.get(i);
                        oldYMC = yushabuMessageContents.get(i);
                        oldYMI.state = YushabuMessageItem.LEFT;
                        oldYMC.state = YushabuMessageContent.LEFT;
                        yushabuMessageItems.set(MESSAGE_MAX_MUN, oldYMI);
                        yushabuMessageContents.set(MESSAGE_MAX_MUN, oldYMC);
                        
                        yushabuMessageItems.set(i, newYMI);
                        yushabuMessageContents.set(i, newYMC);
                    }else {
                        for(int k=0;k<yushabuMessageItems.size();k++) {
                            yushabuMessageItems.get(k).state = YushabuMessageItem.LEFT;
                        }
                        for(int k=0;k<yushabuMessageContents.size();k++) {
                            yushabuMessageContents.get(k).state = YushabuMessageContent.LEFT;
                        }
                        
                        newYMI = new YushabuMessageItem(0
                                , userColor, 32);
                        texture = TextureHelper.loadTexture(context, textBitmap.buildTextBitmap(yushabuMessage.getContent(), 255, 1));
                        leftTexture = TextureHelper.loadTexture(context, textBitmap.buildTextBitmap(yushabuMessage.getContent(), 64, 1));
                        newYMC = new YushabuMessageContent(yushabuMessage.getContent(), 0, texture, leftTexture, 32);
                        
                        newYMI.state = YushabuMessageItem.APPEARING;
                        newYMC.state = YushabuMessageContent.APPEARING;
                        
                        oldYMI = yushabuMessageItems.get(0);
                        oldYMC = yushabuMessageContents.get(0);
                        oldYMI.state = YushabuMessageItem.LEFT;
                        oldYMC.state = YushabuMessageContent.LEFT;
                        yushabuMessageItems.set(MESSAGE_MAX_MUN, oldYMI);
                        yushabuMessageContents.set(MESSAGE_MAX_MUN, oldYMC);
                        
                        yushabuMessageItems.set(0, newYMI);
                        yushabuMessageContents.set(0, newYMC);
                    }
                    break;
                }
            }
        }else {
            for(int i=0;i<yushabuMessageItems.size();i++) {
                if(yushabuMessageItems.get(i).state == YushabuMessageItem.APPEARING) {
                    yushabuMessageItems.get(i).state = YushabuMessageItem.APPEARED;
                }
                if(yushabuMessageContents.get(i).state == YushabuMessageContent.APPEARING) {
                    yushabuMessageContents.get(i).state = YushabuMessageContent.APPEARED;
                }
            }
            newYMI = new YushabuMessageItem(yushabuMessageItems.size()
                    , userColor, 32);
            texture = TextureHelper.loadTexture(context, textBitmap.buildTextBitmap(yushabuMessage.getContent(), 255, yushabuMessageContents.size()+1));
            leftTexture = TextureHelper.loadTexture(context, textBitmap.buildTextBitmap(yushabuMessage.getContent(), 64, yushabuMessageContents.size()+1));
            newYMC = new YushabuMessageContent(yushabuMessage.getContent(), yushabuMessageContents.size(), texture, leftTexture, 32);
            
            newYMI.state = YushabuMessageItem.APPEARING;
            newYMC.state = YushabuMessageContent.APPEARING;
            
            yushabuMessageItems.add(newYMI);
            yushabuMessageContents.add(newYMC);
        }
        
        changePhotoFocusing(currentUserId);
        messageLineEndPoint = newYMI.circleCenter;

    }
    
    public void appendPhoto(User user) {
        String userPhotoPath = user.getPhotoPath();
        final Bitmap userPhoto;
        if(userPhotoPath!=null && userPhotoPath.length()!=0) {
            String friendPhotoName = userPhotoPath.substring(0, userPhotoPath.indexOf("."));
            String friendPhotoExtention = userPhotoPath.substring(userPhotoPath.indexOf(".")+1);
            userPhoto = mEasyStorage.getImageBitmap(friendPhotoName, friendPhotoExtention);
        }else {
            userPhoto = ((BitmapDrawable)(context.getResources().getDrawable(R.drawable.no_profile))).getBitmap();
        }
        int userColor = BackgroundUtil.getAverageColor(userPhoto);
        yushabuPhotoBackgounds.add(
                new YushabuPhotoBackgound(new Point(0f, 0f, 0f)
                ,  userColor, 64));
        /*Drawable userPhotoDrawable = new BitmapDrawable(context.getResources(), userPhoto);*/
        texture = TextureHelper.loadTexture(context, userPhoto);
//        texture = TextureHelper.loadTexture(context, R.drawable.no_profile);
        yushabuPhotos.add(new YushabuPhoto(user.getUserId(),new Point(0f, 0f, 0f), texture, 32));
    }
    
    public void changePhotoFocusing(int userId) {
        //Stop the previous animation
        for(int i=0;i<yushabuPhotos.size();i++) {
            switch(yushabuPhotos.get(i).state) {
            case(YushabuPhoto.FOCUSING):
                yushabuPhotos.get(i).state = YushabuPhoto.FOCUSED;   
                yushabuPhotoBackgounds.get(i).state = YushabuPhotoBackgound.FOCUSED;
                yushabuPhotoBackgounds.get(i).focusingScaleFactor = yushabuPhotoBackgounds.get(i).focusedScaleFactor;
                break;
            case(YushabuPhoto.UNFOCUSING):
                yushabuPhotos.get(i).state = YushabuPhoto.UNFOCUSED;
                yushabuPhotoBackgounds.get(i).state = YushabuPhotoBackgound.UNFOCUSED; 
                yushabuPhotoBackgounds.get(i).focusingScaleFactor = 1f;
                break;
            }
        }
        //Start the new animation
        for(int i=0;i<yushabuPhotos.size();i++) {
            if(yushabuPhotos.get(i).userId == userId) {
                //Set the new focusing photo
                yushabuPhotos.get(i).state = YushabuPhoto.FOCUSING;
                yushabuPhotoBackgounds.get(i).state = YushabuPhotoBackgound.FOCUSING;
                yushabuPhotoBackgounds.get(i).focusingScaleFactor = 1f;
            }
        }
        for(int i=0;i<yushabuPhotos.size();i++) {
            if(yushabuPhotos.get(i).userId != userId) {
                //Set the new unfocusing photo
                switch(yushabuPhotos.get(i).state) {
                case(YushabuPhoto.FOCUSED):
                    yushabuPhotos.get(i).state = YushabuPhoto.UNFOCUSING;
                    yushabuPhotoBackgounds.get(i).state = YushabuPhotoBackgound.UNFOCUSING;
                    yushabuPhotoBackgounds.get(i).focusingScaleFactor = yushabuPhotoBackgounds.get(i).focusedScaleFactor;
                    break;
                default:
                    yushabuPhotos.get(i).state = YushabuPhoto.UNFOCUSED;
                    yushabuPhotoBackgounds.get(i).state = YushabuPhotoBackgound.UNFOCUSED;
                    yushabuPhotoBackgounds.get(i).focusingScaleFactor = 1f;
                }
            }
        }
    }
    
    private float deltTimedChange(float delt, int speed) {
        long deltTime = 45L;
        switch(speed) {
            case(1):
                deltTime = 15L;
                break;
            case(2):
                deltTime = 45L;
                break;
            case(3):
                deltTime = 60L;
                break;
            default:
                deltTime = 45L;
        }
        currentTime = SystemClock.uptimeMillis() / deltTime;
        if(previousTime != currentTime) {
            previousTime = currentTime;
            return delt;
        }
        return 0f;
    }
    
    private void updateAppearingMessageLine() {
        if(messageLineEndPoint == null) {
            return;
        }
        
        float[] previousMessageLineEndPointV = {messageLineEndPoint.x
                                                , messageLineEndPoint.y
                                                , messageLineEndPoint.z
                                                , 1f};
        
        float[] currentMessageLineEndPointV = new float[4];
        
        YushabuMessageItem appearingItem = null;
        for(int i=yushabuMessageItems.size()-1;i>=0;i--) {
            if(yushabuMessageItems.get(i).state == YushabuMessageItem.APPEARING) {
                appearingItem = yushabuMessageItems.get(i);
                break;
            }
        }
        
        setIdentityM(modelMatrix, 0);
        rotateM(modelMatrix, 0, appearingItem.appearingRotateAngle, 0f, 0f, 1f);
        
        multiplyMV(currentMessageLineEndPointV, 0, modelMatrix, 0, previousMessageLineEndPointV, 0);
        
        Point currentMessageLineEndPoint = new Point(currentMessageLineEndPointV[0]
                                                    , currentMessageLineEndPointV[1]
                                                    , currentMessageLineEndPointV[2]);
        
        Point currentMessageStartPoint = null;
        
        for(int i=0;i<yushabuPhotos.size();i++) {
            if(yushabuPhotos.get(i).state == YushabuPhoto.FOCUSED || yushabuPhotos.get(i).state == YushabuPhoto.FOCUSING) {
                currentMessageStartPoint = photoCenterPoints.get(i);
                break;
            }
        }
        
        if(currentMessageStartPoint == null) {
            return;
        }
        
        yushabuMessageLine = new YushabuMessageLine(
                currentMessageStartPoint
              , currentMessageLineEndPoint
              , Color.WHITE, 2);
    }
    
    private Ray convertNormalized2DPointToRay(
            float normalizedX, float normalizedY) {
            // We'll convert these normalized device coordinates into world-space
            // coordinates. We'll pick a point on the near and far planes, and draw a
            // line between them. To do this transform, we need to first multiply by
            // the inverse matrix, and then we need to undo the perspective divide.
            final float[] nearPointNdc = {normalizedX, normalizedY, -1, 1};
            final float[] farPointNdc =  {normalizedX, normalizedY,  1, 1};
            
            final float[] nearPointWorld = new float[4];
            final float[] farPointWorld = new float[4];

            multiplyMV(
                nearPointWorld, 0, invertedViewProjectionMatrix, 0, nearPointNdc, 0);
            multiplyMV(
                farPointWorld, 0, invertedViewProjectionMatrix, 0, farPointNdc, 0);

            // Why are we dividing by W? We multiplied our vector by an inverse
            // matrix, so the W value that we end up is actually the *inverse* of
            // what the projection matrix would create. By dividing all 3 components
            // by W, we effectively undo the hardware perspective divide.
            divideByW(nearPointWorld);
            divideByW(farPointWorld);

            // We don't care about the W value anymore, because our points are now
            // in world coordinates.
            Point nearPointRay = 
                new Point(nearPointWorld[0], nearPointWorld[1], nearPointWorld[2]);
                
            Point farPointRay = 
                new Point(farPointWorld[0], farPointWorld[1], farPointWorld[2]);

            return new Ray(nearPointRay, 
                           Geometry.vectorBetween(nearPointRay, farPointRay));
        }
    
    private void divideByW(float[] vector) {
        vector[0] /= vector[3];
        vector[1] /= vector[3];
        vector[2] /= vector[3];
    }
    
    //Define the touch event
    public void handleTouchPress(float normalizedX, float normalizedY) {
        Ray ray = convertNormalized2DPointToRay(normalizedX, normalizedY);

        //Check whether touch up the history messsage button
        Sphere historyButtonOutside = new Sphere(mostLeftUpPoint,
                BASE_RADIUS);
//        Sphere historyButtonInside = new Sphere(mostLeftUpPoint,
//                BASE_RADIUS - HISTORY_BAR_BG_WIDTH);
        boolean historyButtonOutsidePressed = Geometry.intersects(historyButtonOutside, ray);
//        boolean historyButtonInsidePressed = Geometry.intersects(historyButtonInside, ray);
        if(historyButtonOutsidePressed/* && !historyButtonInsidePressed*/) {
//            context.showToast("History message list");
            context.showHistoryMessages();
            return;
        }
        
        //Check whether touch up the photos
        for(int i=0;i<yushabuPhotos.size();i++) {
            Sphere photoSphere = new Sphere(new Point(
                    (mostLeftUpPoint.x + photoCenterPoints.get(i).x), 
                    (mostLeftUpPoint.y + photoCenterPoints.get(i).y), 
                    (mostLeftUpPoint.z + photoCenterPoints.get(i).z)),
                    PHOTO_FOCUSED_FG_WIDTH/2f);
            boolean photoPressed = Geometry.intersects(photoSphere, ray);
            if(photoPressed) {
//                context.showToast("Photo: " + yushabuPhotos.get(i).userId);
                changePhotoFocusing(yushabuPhotos.get(i).userId);
                return;
            }
        }
        
        //Check whether touch up the message items
        for(int i=0;i<Math.min(MESSAGE_MAX_MUN, yushabuMessageItems.size());i++) {
            Sphere messageItemsOutside = new Sphere(mostLeftUpPoint,
                    BASE_RADIUS + ITEM_BG_WIDTH * (yushabuMessageItems.get(i).itemNumber+1));
            Sphere messageItemsInside = new Sphere(mostLeftUpPoint,
                    BASE_RADIUS + ITEM_BG_WIDTH * (yushabuMessageItems.get(i).itemNumber));
            boolean messageItemsOutsidePressed = Geometry.intersects(messageItemsOutside, ray);
            boolean messageItemsInsidePressed = Geometry.intersects(messageItemsInside, ray);
            /*if(messageItemsOutsidePressed) {
                context.showToast("Outside");
            }
            if(messageItemsInsidePressed) {
                context.showToast("Inside");
            }*/
            //Judge whether press the ring sector, then must don't touch inside and touch outside
            if(messageItemsOutsidePressed && !messageItemsInsidePressed) {
//                context.showToast(yushabuMessageContents.get(i).message);
                context.showCurrentClickedMessage(yushabuMessageContents.get(i).message);
                return;
            }
        }
//        context.showToast("[most left point]x:" + mostLeftUpPoint.x + ", y:" +  mostLeftUpPoint.y + ", y/x: " + mostLeftUpPoint.y/mostLeftUpPoint.x);
//        context.showToast("[Photo 1]x:" + (mostLeftUpPoint.x + photoCenterPoints.get(0).x) + ", y:" +  (mostLeftUpPoint.y + photoCenterPoints.get(0).y)
//                + ", y/x: "+ (mostLeftUpPoint.y + photoCenterPoints.get(0).y)/(mostLeftUpPoint.x + photoCenterPoints.get(0).x));
//        context.showToast("x: " + ray.point.x + ", y:" + ray.point.y + ", y/x: " + ray.point.y/ray.point.x);
        
    }
    
    public void handleTouchDrag(float normalizedX, float normalizedY) {
//        context.showToast("You move around///0.0///");
    }

}
